package org.mtmi.ui.controls;

import java.util.ArrayList;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Path;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.mtmi.tuio.listeners.DragListener;
import org.mtmi.tuio.listeners.SingleTapListener;
import org.mtmi.tuio.listeners.TUIODragEvent;
import org.mtmi.tuio.listeners.TUIOTapEvent;
import org.mtmi.ui.GraphicUtil;
import org.mtmi.ui.IMG;
import org.mtmi.ui.M;
import org.mtmi.ui.controls.listeners.ISelectionListener;
import org.mtmi.ui.controls.listeners.PopupListener;
import org.mtmi.ui.controls.listeners.SelectionEvent;
import org.mtmi.ui.controls.popups.AbstractButtonPopup;
import org.mtmi.ui.controls.popups.ColorPopup;
import org.mtmi.ui.drawables.ColorDrawable;
import org.mtmi.ui.drawables.IDrawable;
import org.mtmi.ui.drawables.KnobColorDrawable;
import org.mtmi.ui.drawables.KnobValueColorDrawable;
import org.mtmi.ui.layouts.LayoutData;
import org.mtmi.ui.scenes.AnimationManager;
import org.mtmi.ui.scenes.COLOR;
import org.mtmi.ui.scenes.FontManager;
import org.mtmi.ui.scenes.Scene;
import org.mtmi.ui.scenes.SceneManager;

/**
 * A control to display a list of item (image, text), and choose one of them.
 * <br>
 * List can be displayed using one (default) or more columns.
 */
public class ListView extends Control implements ITouchableControl, SingleTapListener, DragListener
{
	private static final int MH = 10; //margin horizontal
	private static final int MV = 5; //margin vertical
	private static final int SH = 20; //spacing horizontal
	private static final int CR = 30; //corner radius
	private static final int BR = 10; //bubbler radius
	
	private Object input;
	private ILabelProvider labels;
	private IContentProvider contents;
	private Object selected;
	private int ncols;
	private Object elements[];
	private int w_images[], w_texts[], h_item; //graphical metrics 
	private int c_height, c_y; 
	private ArrayList<ISelectionListener> lis_selection;
	private Color bg_border, fg_border, bg_selection, fg_selection, fg_dot ;
	
	public ListView( Scene scene, int flags ) {
		super(scene, flags);
		ncols = 1;
		setForeground( COLOR.ListForeground ); /// border color
		setBackground( COLOR.ListBackground ); // secondary border color
		bg_border = COLOR.grey0;
		fg_border = COLOR.grey100;
		bg_selection = COLOR.ListSelectionBackground;
		fg_selection = COLOR.ListSelectionForeground;
		fg_dot = COLOR.grey50;
	}
	
	/** @return the number of column the listview used */
	public int getColumnCount() { return ncols; }
	/** Change the number of column the listview used */
	public void setColumnCount( int nc ) { 
		if( nc!=ncols ) {
			ncols = nc;
			updateMetrics();
		}
	}
	
	/** Change the label provider */
	public void setLabelProvider( ILabelProvider l ) { labels = l; }
	/** @return the label provider currently used */
	public ILabelProvider getLabelProvider() { return labels; }
	
	/** change the content provider */
	public void setContentProvider( IContentProvider l ) { contents = l; }
	/** @return the content provider currently used */
	public IContentProvider getContentProvider() { return contents; }

	/** Change input of list view and refresh it */
	public void setInput( Object _input ) {
		if( input!=_input ) {
			input = _input;
			refresh();
		}
	}

	/** refresh list view contents */
	public void refresh() {
		if( labels==null ) throw new IllegalStateException("ILabelProvider is required"); //$NON-NLS-1$
		if( contents==null ) throw new IllegalStateException("IContentProvider is required"); //$NON-NLS-1$
		elements = null;
		w_images = null;
		w_texts = null;
		Object os = selected;
		selected=null;
		if( input!=null ) {
			elements = contents.getElements( input );
			int is=-1;
			for( int i=0; i<elements.length; i++ ) {
				if( elements[0]==os ) { is=i; break; }
			}
			if( is < 0 ) {
				if( elements!=null && elements.length>0 ) {
					selected = elements[0];
				}
			} else {
				selected=elements[is];
			}
			//selection changes
			if( os!=null && selected==null || os==null && selected!=null ) {
				notifySelectionListeners( false );
			}
		}
		updateMetrics();
		redraw();
	}
	
	public Object getSelection() { return selected; }
	
	public void setSelection( Object sel ) {
		if( sel!=null && !sel.equals(selected) || sel==null && selected!=null ) {
			Object nsel = null;
			if( sel!=null && input !=null && contents !=null ) {
				for( int i=0; i<elements.length; i++ ) {
					if( sel.equals(elements[i]) ) {
						nsel = elements[i]; //rather than 'sel', now simple '==' operator works
						break;
					}
				}
			}
			if( nsel!=selected ) {
				selected = nsel;
				redraw();
			}
		}
	}
	
	public void setBackgroundBorder( Color c ) { bg_border=c; }
	public void setForegroundBorder( Color c ) { fg_border=c; }
	
	public void setBackgroundSelection( Color c ) { bg_selection=c; }
	public void setForegroundSelection( Color c ) { fg_selection=c; }
	
	public void setForegroundDot( Color c ) { fg_dot=c; }
	
	private Point images_sizes[];
	
	private void updateMetrics()
	{
		h_item=0;
		w_texts=null;
		w_images=null;
		images_sizes=null;
		if( input==null ) return ;
		w_images = new int[ ncols ];
		w_texts = new int[ ncols ];
		GC gc = new GC( getScene() );
		gc.setFont( FontManager.getInstance().getListFont() );
		try {
			if( elements!=null ) {
				int i=0;
				int col=0;
				for( Object o : elements ) {
					IDrawable img = labels.getImage( o );
					if( img!=null ) {
						if( images_sizes==null ) {
							images_sizes = new Point[elements.length];
						}
						Point s = img.computeSize( 0, 0 );
						w_images[col] = Math.max( w_images[col], s.x );
						h_item = Math.max( h_item, s.y );
					}
					String txt = labels.getText( o );
					if( txt!=null ) {
						Point s = gc.textExtent( txt );
						w_texts[col] = Math.max( w_texts[col], s.x );
						h_item = Math.max( h_item, s.y );
					}
					i++;
					col = (col+1)%ncols;
				}
				h_item += 2*MV;
			}
		} finally {
			gc.dispose();
		}
		int nrow = elements==null ? 0 : (elements.length/ncols);
		if( elements!=null && nrow==0 ) nrow=1;
		c_height = nrow * h_item;
		c_y = 0;
	}
	
	private int getBubbleTouchableR() {
		return Math.max( 30, BR ); //finger require bigger touchable area
	}
	
	@Override
	public boolean containsTouch( TouchControlEvent e ) {
		int by = getBubbleY();
		int br = getBubbleTouchableR();
		if( GraphicUtil.circleContains( location.x+size.x, by, br, e.x, e.y ) ) return true;
		return GraphicUtil.rectContains( location.x, location.y, size.x, size.y, e.x, e.y );
	}
	
	@Override
	public void paint( GC gc )
	{
		Path p = GraphicUtil.getRoundRectangle( gc, location.x, location.y, size.x, size.y, CR,CR ); 
		//gc.setClipping( location.x, location.y, size.x, size.y );
		gc.setClipping( p );
		
		gc.setFont( FontManager.getInstance().getListFont() );
		gc.setBackground( getBackground() );
		gc.fillRectangle( location.x, location.y, size.x, size.y );
		
		if( h_item==0 || elements==null || labels==null ) {
			gc.setClipping( (Rectangle)null );
			return ;
		}
		else {
			int i0 = (c_y/h_item)*ncols;
			int dy = c_y % h_item;
			int y = location.y-dy;
			int yx = location.y+size.y;
			//int xt0 = location.x+MH+w_images+(w_images==0?0:MH);
			int x = location.x+MH;
			int col=0;
			for( int i=i0; i<elements.length && y < yx; i++ ) {
				//background:
				boolean sel =  elements[i] == selected ; 
				if( sel ) {
					gc.setBackground( bg_selection );
					int w = w_images[col]+(w_images[col]==0?0:SH)+w_texts[col]+MH/2;
					//want the last column selection goes to the right of listview
					if( col==ncols-1 && (x-SH/2+w < location.x+size.x) ) {
						w = location.x+size.x - x +SH/2;
					}
					gc.fillRectangle( x-SH/2, y, w, h_item );
					//GraphicUtil.fillRoundRectangle(gc, location.x, y, size.x, h_item, CR, CR );
				} else {
					//gc.setBackground( COLOR.grey90 );
				}
				//image:
				IDrawable img = labels.getImage( elements[i] );
				if( img!= null ) {
					Point s = img.computeSize( w_images[col], h_item-2*MV );
					int xi = x+(w_images[col]-s.x)/2;
					int yi = y+(h_item-s.y)/2;
					img.paint( gc, xi, yi, s.x, s.y, sel ? IDrawable.SELECTED : IDrawable.NORMAL );
				}
				x += w_images[col]+(w_images[col]==0 ? 0 : MH);
				if( sel ) {
					gc.setForeground( fg_selection );
				} else {
					gc.setForeground( getForeground() );
				}
				//text:
				String txt = labels.getText( elements[i] );
				if( txt!=null && !txt.isEmpty() ) {
					Point s = gc.textExtent( txt );
					int yt = y + (h_item-s.y)/2;
					gc.drawString( txt, x, yt, true );
				}
				x += w_texts[col]+SH;
					
				col = (col+1)%ncols;
				if( col==0 ) {
					y += h_item;
					x = location.x+MH;
				}
			}
		}
		
		gc.setClipping( (Rectangle)null );
		
		gc.setForeground( bg_border );
		gc.setLineWidth( 2*borderWidth );
		gc.drawPath( p );
		gc.setForeground( fg_border );
		gc.setLineWidth( borderWidth );
		gc.drawPath( p );
		
		if( c_height > 0 && c_height > size.y ) {
			int y = getBubbleY();
			gc.setBackground( bg_border );
			int R=BR;
			gc.fillOval( location.x+size.x-R, y-R, R+R, R+R );
			gc.setBackground( fg_dot );
			R=BR-2;
			gc.fillOval( location.x+size.x-R, y-R, R+R, R+R );
		}
	}

	private int getBubbleY()
	{
		int h = size.y-CR;
		int y = location.y+CR/2+(h*c_y) / (c_height-size.y);
		return y;
	}
	

	private int evy;
	private int state; //0: none, 1:clicked in item, 2:dragging in item, 3: clicked in bubble, 4: dragging bubble
	
	/** @return index of item, may be  <0 or >= items count */
	private int getIndexAt( int x, int y ) {
		int row = ( y-location.y+c_y)/h_item;
		int col = 0;
		int cx = location.x+MH;
		for( int c=0; c<ncols; c++, col++ ) {
			cx += w_images[c]+ (w_images[c]==0?0:MH)+w_texts[c] + MH/2;
			if( x < cx ) {
				break;
			}
			cx += MH/2;
		}
		if( col >= ncols ) col = ncols;
		int idx = row*ncols+col;
		return idx;
	}
	
	@Override
	public void tapDown(TUIOTapEvent event) {
		evy = event.ey;
		int by = getBubbleY();
		int br = getBubbleTouchableR();
		if( GraphicUtil.circleContains( location.x+size.x, by, br, event.ex, event.ey ) ) {
			state=3;
		}
		else {
			if( h_item > 0 && input !=null && contents!=null ) {
				int i = getIndexAt( event.ex, event.ey );
				if( elements!=null && i>=0 && i<elements.length ) {
					if( elements[i]!=selected ) {
						selected = elements[i];
						redraw();
						notifySelectionListeners( false );
					}
				}
				state = 1;
			}
		}
	}

	@Override
	public void tapUp(TUIOTapEvent event) {
		if( state==1 ) {
			notifySelectionListeners( true );
		}
		state=0;
	}
	
	@Override
	public void drag(TUIODragEvent event) {
		if( state==1 || state == 2 ) {
			state = 2;
			if( c_height>size.y ) {
				int dy = event.ey - evy;
				int ncy = c_y -dy;
				if( ncy < 0 ) ncy=0; else if( ncy > c_height-size.y ) ncy=c_height-size.y;
				if( ncy!= c_y ) {
					c_y = ncy ;
					redraw();
				}
			} else {
				int i = getIndexAt( event.ex, event.ey );
				if( elements!=null && i>=0 && i<elements.length ) {
					if( elements[i]!=selected ) {
						selected = elements[i];
						notifySelectionListeners( false );
						redraw();
					}
				}
			}
		}
		else if( state==3 ) { //state:3
			if( c_height>size.y ) {
				int by = event.ey-location.y-CR/2;
				by = Math.max( by, 0 );
				by = Math.min( by, size.y-CR );
				int h = size.y-CR;
				int ncy = ((c_height-size.y)*by)/h;
				//ncy = Math.max( 0, ncy );
				//ncy = Math.min( ncy, c_height-size.y );
				if( ncy!=c_y ){
					c_y = ncy;
					redraw();
				}
			}
		}
		evy=event.ey;
	}

	@Override
	public Point computeSize(LayoutData l) {
		if( h_item==0 ) {
			return new Point( 100*ncols, 100 );
		} else {
			int w = 0;
			if( w_images!=null ) {
				for( int i=0; i<w_images.length; i++ ) {
					if( i>0 ) w += SH;
					w += w_images[i]+(w_images[i]!=0?MH:0)+w_texts[i];
				}
				w += 2*MH;
			} else {
				w = 100; //default
			}
			return new Point( w, 100 );
		}
	}
	
	public void addSelectionListener( ISelectionListener lis ) {
		if( lis_selection==null ) {
			lis_selection = new ArrayList<ISelectionListener>();
		}
		lis_selection.add( lis );
	}
	
	public void removeSelectionListener( ISelectionListener lis ) {
		if( lis_selection != null ) {
			lis_selection.remove( lis );
		}
	}
	
	private void notifySelectionListeners( boolean confirmed ) {
		if( lis_selection == null || lis_selection.size()==0 ) return ;
		SelectionEvent e = new SelectionEvent( this, selected );
		ISelectionListener lis[] = lis_selection.toArray( new ISelectionListener[ lis_selection.size() ] );
		for( int i=0; i<lis.length; i++ ) {
			if( confirmed ) {
				lis[i].selectionConfirmed( e );
			} else {
				lis[i].selectionChanged( e );
			}
		}
	}
	
	public static void main(String[] args)
	{
		final Display display = new Display();
		
		COLOR.initColors( display );
		
		final Shell shell = new Shell(display);
		//shell.setMaximized (true);
		//shell.setFullScreen(true);
		shell.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
		final SceneManager sceneManager = new SceneManager(shell);
		/*final AnimationManager animationManager =*/ new AnimationManager(shell);

		//macOS require a display to start MidiManager
		//MidiManager midiManager = MidiManager.getInstance();
		//midiManager.init();
		
		Scene scene1 = new Scene( shell );
		
		ILabelProvider lp = new ILabelProvider() {
			@Override
			public String getText(Object element) {
				if( element instanceof RGB ) {
					return "Color";
				} else {
					return "Item "+((Integer)element).toString();
				}
			}
			@Override
			public IDrawable getImage(Object element) {
				if( element instanceof RGB ) {
					RGB rgb = (RGB)element;
					return new ColorDrawable( new Color(null,rgb), true );
				}
				switch( ((Integer)element).intValue() % 7) {
				case 0: return IMG.GetD( IMG.I_EQUALIZER );
				case 1: return IMG.GetD( IMG.I_KEYBOARD );
				case 2:  return new ColorDrawable( COLOR.yellow, false );
				case 3: return IMG.GetD( IMG.I_TRUMPET );
				case 4: return new ColorDrawable( COLOR.dark_blue, true );
				case 5: return new KnobColorDrawable( COLOR.dark_blue );
				case 6: return new KnobValueColorDrawable( COLOR.yellow );
				}
				return null;
			}
		};
		ListView lw = new ListView( scene1, M.SCENE_CONTROL );
		lw.setLabelProvider( lp );
		final Object elm[] = new Object[] { 0, 1, 2, 3, 4, 5, new RGB(100,200,60), 8, 11, 17, 19, 24, 27, 31 };
		lw.setContentProvider( new IContentProvider() {
			@Override
			public Object[] getElements(Object input) {
				return elm ;
			}
		});
		lw.setInput( "INPUT" );
		lw.addSelectionListener( new ISelectionListener() {
			@Override
			public void selectionChanged( final SelectionEvent e ) {
				final Control src = (Control)e.source;
				if( e.selection instanceof RGB ) {
					final RGB rgb = (RGB)e.selection;
					final ColorPopup p = new ColorPopup( src.getScene(), "Choose Color", M.SCENE_CONTROL );
					p.setRGB( rgb );
					p.addResponseListener( new PopupListener() {
						@Override
						public void popupButtonSelected(int index) {
							if( index == AbstractButtonPopup.OK ) {
								RGB c = p.getRGB();
								rgb.red = c.red;
								rgb.green = c.green;
								rgb.blue = c.blue;
							    src.redraw();
							}
						}
					});
				}
				else {
					int v = ((Integer)e.selection).intValue();
					int x = v%7;
					if( x==5||x==6 ) { //see label provider
						ColorPopup p = new ColorPopup( src.getScene(), "Choose Color", M.SCENE_CONTROL );
						//TODO: attendre reponse ....
					}
				}
			}
			@Override
			public void selectionConfirmed(SelectionEvent e) {}
		});
		
		ListView lw2 = new ListView( scene1, M.SCENE_CONTROL );
		lw2.setLabelProvider( lp );
		lw2.setContentProvider( new IContentProvider() {
			@Override
			public Object[] getElements(Object input) {
				Object elm[] = new Object[] { 0, 99 };
				return elm ;
			}
		});
		lw2.setInput( "INPUT" );
		
		Point s = lw.computeSize( null ) ;
		s.y = 250;//Math.min( s.y, 250 );
		lw.setSize( s );
		lw.setLocation( 10, 10 );
		
		lw2.setLocation( lw.getLocation().x+lw.getSize().x+50, lw.getLocation().y );
		lw2.setSize( lw.getSize() );
		
		sceneManager.addScene(scene1);
		
		shell.open();
		
		
		while (!shell.isDisposed()) {
			if (!shell.isDisposed() && !display.readAndDispatch())
				display.sleep();
		}
		//- dispose allocated colors and fonts
		COLOR.disposeColors();
		FontManager.getInstance().dispose();
		//- dispose the TUIO client
		//TODO: TUIOManager.getInstance().dispose();
		//- dispose MIDI stuff
		//TODO: MidiManager.getInstance().dispose();
		display.dispose();
		IMG.dispose();
	}
	
	@Override
	public Rectangle getRedrawArea(Rectangle r) {
		return GraphicUtil.setRect( r, location.x, location.y, size.x+BR, size.y );
	}

}
